在上一篇文章中,我們介紹了如何在舊版 Backstage 中添加 OIDC 插件,並為 Backstage 設定了 SSL 保護以支援 HTTPS,為後續的 IdentityServer 設定做好準備。在本篇文章中,我們將深入探討如何將 OIDC 插件遷移到新版 Backstage 系統,比較新舊版架構在插件應用上的差異。除了將插件與 IdentityServer 結合外,我們還會撰寫解析器邏輯,以便在 OIDC 登入後正確對應到先前抓取到的 AD 員工實體。
根據上一篇在安裝舊版 OIDC 插件時的架構,我們完成了以下幾個重要步驟:
packages/app/src/apis.ts
中定義了 OIDC API,並引用了必要的基底功能和 API 調用端口。此外,我們還在 App.tsx
中新增了 OIDC 的登入選項。packages/backend/src/plugins/auth.ts
中實現了 OIDC 的登入功能和解析器,並在 index.ts
中啟用了這些功能。需要注意的是,index.ts
包含了相當冗長且複雜的設定檔,並引入了自訂插件 auth.ts
。清楚劃分了前端和後端的職責,一切看起來如此合理。
backstage # 專案目錄
├── packages
│ ├── app
│ │ └── src
│ │ ├── api.ts # 定義 OIDC 的 API 方法
│ │ └── App.tsx # 新增 OIDC 的登入選項介面
│ └── backend
│ └── src
│ ├── plugins
│ │ └── auth.ts # 設定插件並定義解析器
│ └── index.ts # 啟用插件
│
└── app-config.yaml
而在新版後端架構一切更為簡潔合理,對於我這種強迫症來說,更喜歡新版的整潔收納。首先將原本包含在 backend/src/plugins
中的一個一個自定插件或模組,轉移到了專案資料夾下的 plugins/
放置,並且每一個模組有自己的資料夾與完整的前端、依賴、註冊、測試、路由的設定,而這些東西都可以通過指令來自動產生,我們將會在後面演示一次。
backstage
├── packages
│ ├── app # 維持不變
│ │
│ └── backend
│ └── src
│ └── index.ts # 加入插件方式改變
├── app-config.yaml
└── plugins
├── example-plugin
├── example-plugin2
└── example-plugin3-backend
由於經過更完整的封裝等操作,我們現在加入插件只需在 backend/src/index.ts
加入一行就可以完成,例如下面這樣的效果。每一個插件都有自己的獨立空間與完善的架構,並且支援快速部署到 npm 空間,也就是說我們也可以將其他人的插件透過 git 抓下來修改,最後一樣加入一行程式啟用,大大增強了開發插件的拓展性與靈活性。
backend.add(import('@internal/backstage-plugin-outlook-api-backend'));
但這也意味著,所有插件都得再經過一定程度的更新修改,才能支援現今的 Backstage。自從推出新版架構後,不管是官方文件或是網路上的討論,時常因過時或不明原因無法使用,由於架構的特殊性,許多錯誤很難知道是什麼問題,只能找官方開發者詢問或是等待 Backstage 更新。這是開發 Backstage 的過程中最花時間、體驗最差的問題,並且開源的特性,每個插件的品質參差不齊。
根據官方的說法與自己實測的結果,如果某些插件尚未遷移到新版系統,可以嘗試這個方式,將舊版插件的寫法轉換成新版系統能讀取的形式,這個轉換器並不總是適用於所有插件,某些特別的插件可能會依賴特定功能,這可能導致在轉換過程中缺少部分實作或參數
這邊舉例 OIDC 插件在測試時,如果透過直接轉換的樣子如下 :
import { legacyPlugin } from '@backstage/backend-common';
const backend = createBackend();
backend.add(legacyPlugin('auth', import('./plugins/auth')));
backend.start();
當時我想,要是能夠直接通過這個轉換就好了。但是,由於該插件是基於 auth
功能擴展的,所以在 Backstage 啟動時,登入相關的 log 記錄會顯示其操作都是由 auth
模組執行的。
在 legacyPlugin
中,第一個參數 auth
就是該插件的名稱。當使用登入驗證相關功能時,Backstage 預設會從 auth
插件中尋找對應的功能。不幸的是,如果 OIDC 插件以這種方式加入 Backstage,會與新版的預設驗證模組發生衝突,導致系統提示你重複引用名為 auth
的模組。你可能會想到直接更改名稱,由於預設的運作邏輯,Backstage會 auth
模組中找不到 OIDC 插件設定,將導致插件無法生效。
// auth plugin
backend.add(import('@backstage/plugin-auth-backend'));
為了解決這類問題,許多插件已經進行了更新。根據官方文件,大多數插件在引入新版系統時,會在名稱末尾加上 /alpha
,例如以下這個插件:
backend.add(import('@backstage/plugin-catalog-backend-module-aws/alpha'));
既然沒有現成的遷移版本,很幸運的我們能夠自行遷移到新版系統,更棒的是在遷移 OIDC 插件的過程,我們並不需要修改模組的程式,也非常適合用來小試身手。
現在我們回到新版系統上的專案目錄,透過 yarn new
指令來新增一個插件,可以看到 Bcakstage 提供了許多開發範本,只需要命名就可以自動產生完畢相關需要的組件與設定,讓我們可以專注在開發插件功能,這邊我們選擇 backend-module
,來幫 auth
模組拓展加入 OIDC 功能。
第一個輸入要拓展的既有模組 auth
,再來輸入自己命名的插件名稱,我命名為 oidc
,接著 Backstage 就會自動產生檔案並安裝好依賴,順利的話就會跳出成功訊息,並且看到左邊的 plugins
資料夾多出了剛裝好的插件。
最新版的 Backstage 會自動將插件加入到 backend/src/index.ts
中。
進到剛剛新增的插件其中的module.ts
,這是預設的範本程式可以看到這個架構其實與舊版非常相似。接下來我們要加入 OIDC 的功能到 Backstage 中。
幸好我們可以在文件中找到別的插件的結構,同為拓展 Auth 功能,所以我們可以很輕易的模仿 OIDC 的版本。
根目錄輸入指令安裝 OIDC 身份驗證模組
yarn --cwd packages/backend add @backstage[/plugin-auth-backend-module-oidc-provider](https://www.npmjs.com/package/@backstage/plugin-auth-backend-module-oidc-provider)
新增到 packages/backend/src/index.ts
backend.add(import('@backstage/[plugin-auth-backend-module-oidc-provider](https://www.npmjs.com/package/@backstage/plugin-auth-backend-module-oidc-provider)'));
再來就是參考前一篇的部分,在 apis.ts
定義 API,在對應的地方加入以下程式碼,並 import 參考的功能。
import {
AnyApiFactory,
ApiRef, //new
BackstageIdentityApi, // new
configApiRef,
createApiFactory,
createApiRef, discoveryApiRef, oauthRequestApiRef, // new
OpenIdConnectApi, // new
ProfileInfoApi, // new
SessionApi, // new
} from '@backstage/core-plugin-api';
import { OAuth2 } from '@backstage/core-app-api'; // new
...
export const identityserverOIDCAuthApiRef: ApiRef<
OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi
> = createApiRef({
id: 'auth.sso-auth-provider',
});
...
createApiFactory({
api: identityserverOIDCAuthApiRef,
deps: {
discoveryApi: discoveryApiRef,
oauthRequestApi: oauthRequestApiRef,
configApi: configApiRef,
},
factory: ({ discoveryApi, oauthRequestApi, configApi }) => OAuth2.create({
configApi,
discoveryApi,
oauthRequestApi,
provider: {
id: 'sso-auth-provider',
title: 'SSO auth provider',
icon: () => null,
},
environment: configApi.getOptionalString('auth.environment'),
defaultScopes: ['openid', 'profile', 'offline_access'],
popupOptions: {
// optional, used to customize login in popup size
size: {
fullscreen: true,
},
/**
* or specify popup width and height
* size: {
width: 1000,
height: 1000,
}
*/
},
}),
}),
接著我們來改寫剛剛新增的擴充插件邏輯 plugins/auth-backend-module-oidc/src/module.ts
import {
coreServices,
createBackendModule,
} from '@backstage/backend-plugin-api';
import { oidcAuthenticator } from '@backstage/plugin-auth-backend-module-oidc-provider';
import {
authProvidersExtensionPoint,
createOAuthProviderFactory,
} from '@backstage/plugin-auth-node';
import {
DEFAULT_NAMESPACE,
stringifyEntityRef,
} from '@backstage/catalog-model';
export const authModuleOidc = createBackendModule({
pluginId: 'auth',
moduleId: 'oidc',
register(reg) {
reg.registerInit({
deps: {
providers: authProvidersExtensionPoint,
logger: coreServices.logger,
},
async init({ providers, logger }) {
providers.registerProvider({
providerId: 'sso-auth-provider',
factory: createOAuthProviderFactory({
authenticator: oidcAuthenticator,
async signInResolver(info, ctx) {
logger.info('正在使用 OIDC 身份驗證');
const userRef = stringifyEntityRef({
kind: 'User',
name: info.result.fullProfile.userinfo.sub,
namespace: DEFAULT_NAMESPACE,
});
return ctx.issueToken({
claims: {
sub: userRef,
ent: [userRef],
},
});
},
}),
});
},
});
},
});
為後端加入 OIDC 插件與我們展的模組結果應會長這樣,基礎的 auth 插件是不能刪除的,下面的 github、oidc 驗證提供者插件基本上都是基於 auth 去擴充。
最後加入設定到 app-config.yaml
,以及新增登入選項的介面到 App.tsx
,詳細可以參考上一篇的程式碼,這邊先以截圖結果的樣子呈現。
接下來是實做身份驗證服務的部分,透過 IdentityServer 提供的其中一個 QuickStart 範例專案,我們可以快速啟用一個最基本的 OIDC 驗證,我們將使用它來測試串接 Backstage 做登入功能。
對於單獨下載 github 中某個資料夾或檔案,推薦使用此工具 DownGit,可以直接將 IdentityServer QuickStart 的網址貼進去單獨下載。
https://github.com/DuendeSoftware/Samples/tree/main/IdentityServer/v7/Quickstarts/2_InteractiveAspNetCore
這個範例中包含了 IdentityServer 的 Server 端,也包含了讓我們快速測試的 client 端,讓我們直接啟用專案就可以體驗 SSO 的登陸過程,所以我們可以把 clinet 端的部分改成連結我們 Backstage 服務,您只需要修改Config.cs
即可,這裡範本給了兩種 client 登入驗證方式,要實作使用登入效果我們選擇下方的 code 驗證流程。
確保RedirectUris
設定為https://localhost:7007/api/auth/sso-auth-provider/handler/frame
,這是 Backstage OIDC 的預設值,其他值可以按照圖中所例。
設定完畢後可以將 IdentityServer 與 Backstage 同時啟用,登入 Backstage 選項選擇 OIDC 並填入IdentityServer 預設的範例使用者帳密 bob/bob
,恭喜我們終於完成了整個步驟。
在本篇介紹了 Backstage 在新舊版系統下的差異,這點在開發插件時時常發生,但隨著 Backstage 不斷更新都以新版系統為主,我們只了解其中的變化與處理方式,例如本文的 OIDC 舊插件遷移的案例,相信在社群的不斷更新下,未來也不需要經過這樣的操作。
至此,關於 OIDC 插件的演示就到此結束,我們已成功為 Backstage 增加了 SSO 功能。未來在將其他平台服務整合至 Backstage 時,可以透過這一 SSO 機制,實現只需登入 Backstage,即可在結合的其他平台中使用相同的身份驗證。
關於 Backstage 的新舊版文件遷移狀態可以參考這個 issue,在開發時隨時關注最新的進展,也許可以幫助自己省下非常多時間~
https://backstage.io/docs/auth/oidc/
https://medium.com/@jincoco/backstage-io-oidc-authentication-with-duende-identityserver-a6c076eb69d0
https://www.youtube.com/watch?v=Xncis04BhQs
https://backstage.io/docs/backend-system/building-backends/migrating
https://medium.com/@jincoco/backstage-io-migrating-oidc-auth-module-to-the-new-backend-system-aa9e12786204
https://www.npmjs.com/package/@backstage/plugin-auth-backend-module-oidc-provider
https://minhaskamal.github.io/DownGit